﻿namespace LogicStudio.Framework.UI.Comandos
{
    using Comun;
    using System;
    using System.ServiceModel.DomainServices.Client;
    using System.Windows;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Controls;
    using Utilidades;
    using System.Reflection;

    /// <summary>
    /// Comando genérico que se utiliza como comando base para mostrar un editor
    /// </summary>
    /// <typeparam name="TEntidad">
    /// Tipo de la entidad predominante o principal
    /// </typeparam>
    /// <typeparam name="TEditor">
    /// Tipo del editor a utilizarce en el mantenimiento
    /// </typeparam>
    /// <typeparam name="TContexto">
    /// Tipo del contexto a utilizarce en el mantenimiento
    /// </typeparam>
    public class ComandoEditorGenerico<TContexto, TEntidad, TEditor> : ICommand
        where TContexto : DomainContext
        where TEditor : ChildWindow, new()
    {
        #region Atributos

        private readonly IObjetoModelo<TContexto, TEntidad> _modelo;
        private ChildWindow _editor;
        private DataForm _formulario;

        #endregion

        #region Constantes

        private const string EtiquetaModeloAux = "ModeloAux";
        private const string Titulo = "Editor";

        #endregion

        #region Eventos

        /// <summary>
        /// Evento para controlar el cambio de estado del comando
        /// </summary>
        public event EventHandler CanExecuteChanged;

        #endregion

        #region Constructores

        /// <summary>
        /// Inicializamos una instancia de Comando Base
        /// </summary>
        /// <param name="modelo">Objeto que se utiliza de contexto para el editor genérico</param>
        public ComandoEditorGenerico(IObjetoModelo<TContexto, TEntidad> modelo)
        {
            _modelo = modelo;
            if (CanExecuteChanged != null)
            {
                CanExecuteChanged(this, null);
            }
        }

        #endregion

        #region Metodos Privados

        /// <summary>
        /// Método de busqueda de un objeto de dependencia
        /// </summary>
        /// <param name="objBuscado">
        /// Parametro de salida mediante referencia que retornará el objeto buscado
        /// </param>
        /// <param name="objetoPadre">
        /// El objeto padre en el que se buscara un objeto deseado
        /// </param>
        /// <param name="funcionCriterio">
        /// Funcion de criterio de busqueda del objeto
        /// </param>
        private static void BuscarObjeto(ref DependencyObject objBuscado, DependencyObject objetoPadre, Func<DependencyObject, bool> funcionCriterio)
        {
            if (objBuscado != null)
            {
                return;
            }

            var elementoPadre = objetoPadre as FrameworkElement;
            if (elementoPadre != null)
            {
                var cantidadItems = VisualTreeHelper.GetChildrenCount(elementoPadre);
                for (var i = 0; i < cantidadItems; i++)
                {
                    var elementoHijo = VisualTreeHelper.GetChildrenCount(elementoPadre) == 0 ?
                        null : VisualTreeHelper.GetChild(elementoPadre, i);
                    if (funcionCriterio(elementoHijo))
                    {
                        objBuscado = elementoHijo;
                        break;
                    }
                    BuscarObjeto(ref objBuscado, elementoHijo, funcionCriterio);
                }
            }
            return;
        }

        /// <summary>
        /// Valida si se debe cerrar o no un formulario
        /// </summary>
        /// <param name="sender">Control que envia la acción validarse</param>
        /// <param name="e">Argumento de tipo teclado</param>
        private void CerrarVentana(object sender, KeyEventArgs e)
        {
            if (e.Key == Key.Escape)
            {
                CerrarEditor();
            }
        }

        /// <summary>
        /// Cierra el formulario editor
        /// </summary>
        private void CerrarEditor()
        {
            _formulario.CancelEdit();
            _editor.DialogResult = false;
        }

        /// <summary>
        /// Función que obtiene el titulo del Editor
        /// </summary>
        /// <returns>
        /// Una cadena que contiene el titulo del editor
        /// </returns>
        private string ObtenerTituloEditor()
        {
            var titulo = Titulo;
            var recurso = LogicStudio.Framework.UI.Utilidades.RecursosLenguaje.Recursos;
            switch (_modelo.Modo)
            {
                case ModoEntidad.Editar:
                    titulo = string.Format(recurso.GetType().GetProperty("TituloEditar").GetValue(titulo, null).ToString(), _modelo.NombreEntidad);
                    break;
                case ModoEntidad.Nuevo:
                    titulo = string.Format(recurso.GetType().GetProperty("TituloAgregar").GetValue(titulo, null).ToString(), _modelo.NombreEntidad);
                    break;
                case ModoEntidad.SoloLectura:
                    titulo = string.Format(recurso.GetType().GetProperty("TituloMostrar").GetValue(titulo, null).ToString(), _modelo.NombreEntidad);
                    break;
            }
            return titulo;
        }

        /// <summary>
        /// Crea una instancia del editor generico y lo despliega
        /// </summary>
        private void MostrarEditor()
        {
            if (Application.Current.Resources[EtiquetaModeloAux] != null)
            {
                Application.Current.Resources.Remove(EtiquetaModeloAux);
            }
            Application.Current.Resources.Add(EtiquetaModeloAux, _modelo);

            _editor = new TEditor { DataContext = _modelo, Title = ObtenerTituloEditor() };

            var formulario = default(DependencyObject);

            var tipoContenido = _editor.Content.GetType();
            if (tipoContenido == typeof(BusyIndicator))
            {
                var contenido = _editor.Content as BusyIndicator;
                if (contenido != null)
                {
                    BuscarObjeto(ref formulario, contenido.Content as DependencyObject, item => item is DataForm);
                }
            }
            else
            {
                BuscarObjeto(ref formulario, _editor.Content as DependencyObject, item => item is DataForm);
            }

            if (formulario != null)
            {
                _formulario = (DataForm)formulario;
                _formulario.AutoGeneratingField += _modelo.PersonalizarEditor;
                _formulario.BeginEdit();
            }

            _editor.KeyDown += CerrarVentana;
            _editor.Show();
        }

        #endregion

        #region Metodos Publicos

        public bool CanExecute(object parameter)
        {
            return true;
        }

        /// <summary>
        /// Metodo que se ejecuta al invocar al ComandoEditorGenerico
        /// </summary>
        /// <param name="parameter">
        /// Parametro que indica el estado de la entidad
        /// Valores Disponibles: Nuevo, Editar, Eliminar, SoloLectura
        /// </param>
        public void Execute(object parameter)
        {
            var modo = (ModoEntidad)Enum.Parse(typeof(ModoEntidad), parameter.ToString(), true);
            _modelo.Modo = modo;
            if (modo == ModoEntidad.Eliminar)
            {
                return;
            }
            MostrarEditor();
        }

        /// <summary>
        /// Valida el ocultamiento del formulario de edición.
        /// </summary>
        /// <param name="resultado">Indica si el usuario desea o no aplicar los cambios</param>
        /// <param name="cerrarEditor">
        /// Metodo que se jecutará si la accion de grabar termina satisfactoriamente
        /// </param>
        /// <returns>
        /// Verdadero si el usuario indicó que desea grabar los cambio y el formulario cumple
        /// con la validación de datos<br />
        /// Falso si el usuario indicó que no desea grabar los cambios o el formulario se encontraba
        /// en modo de lectura<br />
        /// Nulo si el usuario indico que desea grabar los cambio y el formulario no cumple
        /// con la validación de datos, en este caso el editor permanecera visible
        /// </returns>
        public bool? OcultarEditor(bool resultado, out Action cerrarEditor)
        {
            cerrarEditor = null;
            if (_modelo.Modo == ModoEntidad.SoloLectura || !resultado)
            {
                CerrarEditor();
                _modelo.Contexto.RejectChanges();
                return false;
            }

            _formulario.CommitEdit();

            if (_formulario.ValidateItem())
            {
                if (!_modelo.ValidarItems())
                {
                    return null;
                }

                cerrarEditor = CerrarEditor;
                return true;
            }

            return null;
        }

        #endregion
    }
}